﻿namespace Cellfish.Toolbox.Facts.Tasks
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    using Cellfish.Test;
    using Cellfish.Toolbox.Tasks;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class TaskWhen_LogicalFacts
    {
        [TestMethod]
        public void Given_null_task_When_waiting_for_Not_Then_ArgumentNullException_is_thrown()
        {
            Ensure.Throws<ArgumentNullException>(() => TaskWhen.Not(null));
        }

        [TestMethod]
        public void Given_cancelled_task_When_waiting_for_Not_Then_task_fails()
        {
            var task = Task.Run(() => true, new CancellationToken(true));
            Ensure.TaskCompletes(task, TimeSpan.FromSeconds(5));
            Ensure.TaskFails<TaskCanceledException>(TaskWhen.Not(task));
        }

        [TestMethod]
        public void Given_completed_task_When_waiting_for_Not_Then_negated_result_is_returned()
        {
            Assert.IsFalse(TaskWhen.Not(Task.FromResult(true)).Result, "Wrong result when negating true");
            Assert.IsTrue(TaskWhen.Not(Task.FromResult(false)).Result, "Wrong result when negating false");
        }

        [TestMethod]
        public void Given_boolean_task_When_waiting_for_Not_Then_negated_result_is_returned()
        {
            var completeTasks = new ManualResetEvent(false);
            var taskTrueStarted = new ManualResetEvent(false);
            var taskFalseStarted = new ManualResetEvent(false);
            var taskTrue = Task.Run(
                () =>
                    {
                        taskTrueStarted.Set();
                        completeTasks.WaitOne();
                        return true;
                    });
            var taskFalse = Task.Run(
                () =>
                    {
                        taskFalseStarted.Set();
                        completeTasks.WaitOne();
                        return false;
                    });
            taskTrueStarted.WaitOne();
            taskFalseStarted.WaitOne();
            var resultFalse = TaskWhen.Not(taskTrue);
            var resultTrue = TaskWhen.Not(taskFalse);
            completeTasks.Set();
            Assert.IsFalse(resultFalse.Result, "Wrong result when negating true");
            Assert.IsTrue(resultTrue.Result, "Wrong result when negating false");
        }

        [TestMethod]
        public void Given_failing_task_When_waiting_for_Not_Then_result_fails()
        {
            var taskStarted = new ManualResetEvent(false);
            var completeTask = new ManualResetEvent(false);
            var task = Task.Run(
                () =>
                    {
                        taskStarted.Set();
                        if (completeTask.WaitOne())
                        {
                            throw new InvalidOperationException();
                        }

                        return true;
                    });
            taskStarted.WaitOne();
            var resultTask = TaskWhen.Not(task);
            completeTask.Set();
            Ensure.TaskFails<InvalidOperationException>(resultTask);
        }

        [TestMethod]
        public void Given_no_task_When_waiting_for_Or_Then_false_task_is_returned()
        {
            Assert.IsFalse(TaskWhen.Or().Result);
        }

        [TestMethod]
        public void Given_null_task_When_waiting_for_Or_Then_ArgumentNullException_is_thrown()
        {
            Ensure.Throws<ArgumentNullException>(() => TaskWhen.Or(null));
        }

        [TestMethod]
        public void Given_one_task_When_waiting_for_Or_Then_result_is_given_task()
        {
            var task = Task.Run(() => true);
            Assert.AreSame(task, TaskWhen.Or(task));
        }

        [TestMethod]
        public void Given_two_false_tasks_When_waiting_for_Or_Then_result_is_false_task()
        {
            Assert.IsFalse(TaskWhen.Or(Task.FromResult(false), Task.FromResult(false)).Result);
        }

        [TestMethod]
        public void Given_two_tasks_When_waiting_for_Or_and_second_task_completes_first_with_true_Then_result_is_completed_true_task()
        {
            var taskTrueComplete = new ManualResetEvent(false);
            var taskFalseComplete = new ManualResetEvent(false);
            var taskTrue = Task.Run(
                () =>
                    {
                        taskTrueComplete.WaitOne();
                        return true;
                    });
            var taskFalse = Task.Run(
                () =>
                {
                    taskFalseComplete.WaitOne();
                    return false;
                });
            var result = TaskWhen.Or(taskFalse, taskTrue);
            taskTrueComplete.Set();
            Assert.IsTrue(result.Result);
            taskFalseComplete.Set();
        }

        [TestMethod]
        public void Given_no_task_When_waiting_for_And_Then_false_task_is_returned()
        {
            Assert.IsFalse(TaskWhen.And().Result);
        }

        [TestMethod]
        public void Given_null_task_When_waiting_for_And_Then_ArgumentNullException_is_thrown()
        {
            Ensure.Throws<ArgumentNullException>(() => TaskWhen.Or(null));
        }

        [TestMethod]
        public void Given_one_task_When_waiting_for_And_Then_result_is_given_task()
        {
            var task = Task.Run(() => true);
            Assert.AreSame(task, TaskWhen.Or(task));
        }

        [TestMethod]
        public void Given_two_true_tasks_When_waiting_for_And_Then_result_is_true_task()
        {
            Assert.IsTrue(TaskWhen.And(Task.FromResult(true), Task.FromResult(true)).Result);
        }

        [TestMethod]
        public void Given_two_tasks_When_waiting_for_And_and_second_task_completes_first_with_false_Then_result_is_completed_false_task()
        {
            var taskTrueComplete = new ManualResetEvent(false);
            var taskFalseComplete = new ManualResetEvent(false);
            var taskTrue = Task.Run(
                () =>
                {
                    taskTrueComplete.WaitOne();
                    return true;
                });
            var taskFalse = Task.Run(
                () =>
                {
                    taskFalseComplete.WaitOne();
                    return false;
                });
            var result = TaskWhen.And(taskTrue, taskFalse);
            taskFalseComplete.Set();
            Assert.IsFalse(result.Result);
            taskTrueComplete.Set();
        }
    }
}
